﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;

namespace AbstractClass.Configuration
{
    /// <summary>
    /// Provides access to configuration files.
    /// </summary>
    /// <remarks>This class is a singleton class.</remarks>
	[DataContract]
	public class ConfigurationManager
	{
		private static ConfigurationManager _configManager;
		[DataMember]
		private IList<object> _configurationCatalog;
		
		private ConfigurationManager()
		{
			_configurationCatalog = new List<object>();
		}

        /// <summary>
        /// Gets the current configuration manager instance.
        /// </summary>
        /// <value>The current instance.</value>
		public static ConfigurationManager Instance
		{
			get
			{
				if(_configManager == null)
					throw new InvalidOperationException(LanguageResource.LoadConfigurationFirst);
				return _configManager;
			}
		}

        /// <summary>
        /// Loads the configurations from a specified path.
        /// </summary>
        /// <param name="configPath">The configuration storage path.</param>
        /// <returns>A <see cref="ConfigurationManager"/> instance obtained from the configuration file.</returns>
		public static ConfigurationManager Load(string configPath)
		{
			string xml = string.Empty;
			try
			{
				if(File.Exists(configPath))
					xml = File.ReadAllText(configPath);
			}
// ReSharper disable EmptyGeneralCatchClause
			catch
// ReSharper restore EmptyGeneralCatchClause
			{
			}
			
			_configManager = LoadFromXML(xml);
			
			var assemblies = AppDomain.CurrentDomain.GetAssemblies();
			var types = assemblies.SelectMany(a => a.GetTypes());
			
			var configTypes = types.Where(t => t.GetCustomAttributes(typeof(ConfigurationElementAttribute), false).Length > 0);
			
			var configObjects = configTypes.Select(Activator.CreateInstance);
			
			foreach (var config in from config in configObjects
			                       let configInner = config
			                       let found = _configManager._configurationCatalog.Any(element => element.GetType().Equals(configInner.GetType()))
			                       where !found
			                       select config)
			{
			    _configManager._configurationCatalog.Add(config);
			}
			
			var removedElements = (from config in _configManager._configurationCatalog
			                    let found = configObjects.Any(element => element.GetType().Equals(config.GetType()))
			                    where !found
			                    select config).ToList();

            foreach (var element in removedElements) {
				_configManager._configurationCatalog.Remove(element);
			}
			
			return _configManager;
		}

        /// <summary>
        /// Gets a configuration element of type <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">The type of the configuration.</typeparam>
        /// <returns>A configuration element of type <typeparamref name="T"/>.</returns>
        /// <remarks>The type should be marked with <see cref="ConfigurationElementAttribute"/> to be recognized as
        /// a valid configuration element.</remarks>
		public static T GetElement<T>()
		{
			foreach (var module in _configManager._configurationCatalog) {
				if(module.GetType().Equals(typeof(T)))
					return (T)module;
			}
			throw new ArgumentException(
				string.Format(LanguageResource.ConfigurationElementNotFound, typeof(T)));
		}

        /// <summary>
        /// Saves the configurations to a specified path.
        /// </summary>
        /// <param name="configPath">The configuration storage path.</param>
        /// <returns><c>true</c> if the configuration is saved successfully; otherwise, <c>false</c>.</returns>
		public bool Save(string configPath)
		{
			try
			{
				var xml = SaveToXML();
				File.WriteAllText(configPath, xml);
				return true;
			}
			catch
			{
				return false;
			}
		}
		
		private string SaveToXML()
		{
			using (var ms = new MemoryStream())
			{
				var ser = new NetDataContractSerializer();
				ser.Serialize(ms, this);
				ms.Flush();

				ms.Position = 0;
				var streamReader = new StreamReader(ms);
				string text = streamReader.ReadToEnd();
				return text;
			}
		}
		
		private static ConfigurationManager LoadFromXML(string xml)
		{
			try
			{
				using (var ms = new MemoryStream())
				{
					var sw = new StreamWriter(ms);
					sw.Write(xml);
					sw.Flush();
					ms.Position = 0;

					var ser = new NetDataContractSerializer();
					var configMgr = (ConfigurationManager) ser.Deserialize(ms);

					return configMgr;
				}
			}
			catch
			{
				return new ConfigurationManager();
			}
		}
	}
}
